/*************************************************************
 *Name:           Christopher Alexander
 *Email address:  alexanc2@onid.oregonstate.edu
 *Class name:     CS362-400
 *Assignment:     Team 9 - Project Test Game Initialization
 *Description:    The program will peform unit testing on the
 *                dominion.c function initializeGame(). If values
 *                for the parameters of initializeGame() are not
 *                supplied by the user, they will be generated
 *                randomly by the program.  Both valid and invalid
 *                inputs to initializeGame() will be tested.
 *************************************************************/

#include "dominion.h"
#include "interface.h"
#include <stdio.h>
#include "rngs.h"
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>

#define SEED_MIN 0
#define SEED_MAX 1000
#define PLAYERS_MIN 2
#define PLAYERS_MAX 4
#define NUM_CARD_TYPES 27
#define NUM_KCARD_TYPES 20

/*Function Prototypes*/
int* setRandomGameInitializers(struct gameState *state);
int* setKingdomCards(void);
void setPlayerCardPiles(struct gameState *state);
int preIsGameOver(struct gameState *preState);
int preScoreFor (int, struct gameState *preState);
void testIsGameOver(struct gameState *state);
void testScoreFor(struct gameState *state);
int testWhoseTurn(int, struct gameState *state);
void testGetWinners(struct gameState *state);
void runTests(void);


//Global variables.
int numPlayers;
int numSeed;


/*If command line args have not been used to set initial game conditions,
 *this function will randomly set those variables.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setRandomGameInitializers(struct gameState *state) {
   int *kingCards;
   int i;

   //Set the number of players to be between 2-4
   numPlayers = (rand() % 3) + 2;

   //Set the seed to use in initializeGame.
   numSeed = (rand() % SEED_MAX) + 1;

   //Fill the gameState with random garbage.
   if ((rand() % 50) == 1){
      for (i = 0; i < sizeof(struct gameState); i++){
            ((char*)state)[i] = (int) (Random() * 256);
      }
   }

   //Randomly assign kingdomcards.
   kingCards = setKingdomCards();

   return kingCards;
}


/*Randomly choose 10 kingdom cards to play a game with.
 *Params: None.
 *Return: a pointer to an array of randomly selected kingdom cards.
 */
int* setKingdomCards() {
   int i;
   int usedCards[NUM_CARD_TYPES]; //Will keep track of cards that have been assigned.
   int* k = malloc(10 * sizeof(int));

   for(i = 0; i < 10; i++) {
      //Assign a kingdom card to this position in array.  The kingdom cards are in postions
      //7-26 in enum CARD (see dominion.h)
      k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      //If the card is a duplicate, reassign until a unique card is chosen.
      while (usedCards[ (k[i]) ] == -1) {
         k[i] = (rand() % NUM_KCARD_TYPES) + 7;
      }
      //This kingdom card has now been chosen and must not be chosen again.
      usedCards[ (k[i]) ] = -1;
   }

   return k;
}


/*Randomly puts scoring cards in the hand, deck and discard pile of each
 *player for the purpose of testing the functions in this module.
 *Params: state - a pointer to a struct gameState.
 *Return: None.
 */
void setPlayerCardPiles(struct gameState *state){
   int scoreCards[6] = {curse, estate, duchy, province, great_hall, gardens};
   int randomCard;
   int i, j;

   //Randomly set size of deck, discard, and hand for each player.
   //Range of 0-9 is used to increase the chance of tie games.
   for (i = 0; i < state->numPlayers; i++){
      state->deckCount[i] = (int) (Random() * 10);
      state->discardCount[i] = (int) (Random() * 10);
      state->handCount[i] = (int) (Random() * 10);
   }

   //For each player, randomly fill their decks with scoring cards.
   for (i = 0; i < state->numPlayers; i++){
      for (j = 0; j < state->deckCount[i]; j++){
         randomCard = (int) (Random() * 6);
         state->deck[i][j] = scoreCards[randomCard];
      }

      for (j = 0; j < state->discardCount[i]; j++){
         randomCard = (int) (Random() * 6);
         state->discard[i][j] = scoreCards[randomCard];
      }

      for (j = 0; j < state->handCount[i]; j++){
         randomCard = (int) (Random() * 6);
         state->hand[i][j] = scoreCards[randomCard];
      }
   }

}


/*This function is used to generate a value for gameOver that
 *should be identical to the value calculated by isGameOver()
 *Params: preState - a pointer to a struct gameState.
 *Return: 1 - if the game is over.
 *        0 - if the game is not over.
 */
int preIsGameOver(struct gameState *state) {

   int i;
   int numEmptyPiles;

   //if stack of Province cards is empty, the game ends
   if (state->supplyCount[province] == 0){
      return 1;
   }

   //if three supply pile are at 0, the game ends
   numEmptyPiles = 0;
   for (i = 0; i < NUM_CARD_TYPES; i++){
      if (state->supplyCount[i] == 0){
         numEmptyPiles++;
      }
   }
   if ( numEmptyPiles >= 3){
      return 1;
   }

   return 0;
}


/*This function is used to generate a value for score that
 *should be identical to the value calculated by scoreFor()
 *Params: p - the player to check the score of.
 *        preState - a pointer to a struct gameState that contains.
                     a score to be calculated.
 *Return: the score of player p.
 */
int preScoreFor (int p, struct gameState *preState) {

   int i;
   int score = 0;

   //score from hand
   for (i = 0; i < preState->handCount[p]; i++){
      if (preState->hand[p][i] == curse) { score = score - 1; };
      if (preState->hand[p][i] == estate) { score = score + 1; };
      if (preState->hand[p][i] == duchy) { score = score + 3; };
      if (preState->hand[p][i] == province) { score = score + 6; };
      if (preState->hand[p][i] == great_hall) { score = score + 1; };
      if (preState->hand[p][i] == gardens) { score = score + ( fullDeckCount(p, 0, preState) / 10 ); };
   }

   //score from discard
   for (i = 0; i < preState->discardCount[p]; i++){
      if (preState->discard[p][i] == curse) { score = score - 1; };
      if (preState->discard[p][i] == estate) { score = score + 1; };
      if (preState->discard[p][i] == duchy) { score = score + 3; };
      if (preState->discard[p][i] == province) { score = score + 6; };
      if (preState->discard[p][i] == great_hall) { score = score + 1; };
      if (preState->discard[p][i] == gardens) { score = score + ( fullDeckCount(p, 0, preState) / 10 ); };
   }

   //score from deck
   for (i = 0; i < preState->deckCount[p]; i++){
      if (preState->deck[p][i] == curse) { score = score - 1; };
      if (preState->deck[p][i] == estate) { score = score + 1; };
      if (preState->deck[p][i] == duchy) { score = score + 3; };
      if (preState->deck[p][i] == province) { score = score + 6; };
      if (preState->deck[p][i] == great_hall) { score = score + 1; };
      if (preState->deck[p][i] == gardens) { score = score + ( fullDeckCount(p, 0, preState) / 10 ); };
   }
   return score;
}


/*This function will determine if the game is over in two ways,
 *and then compare the results to make sure they are the same.
 *1)The isGameOver() function of dominion.c will be used.
 *2)It will be determined using the preIsGameOver() function here.
 *Params: preState - a pointer to a struct gameState.
 *Return: None.
 */
void testIsGameOver(struct gameState *state){
   int testOver, preOver; //Used for testing isGameOver().

   //Determine if the game is over in two different ways.
   preOver = preIsGameOver(state);
   testOver = isGameOver(state);

   //Check that isGameOver() is correct.
   assert(preOver == testOver);
}


/*This function will calculate the score of a player in two ways,
 *and then compare the results to make sure they are the same.
 *1)The scoreFor() function of dominion.c will be used to calculate score.
 *2)The score will be calculated using the preScoreFor() function here.
 *Params: state - a pointer to a struct gameState that contains
 *                a score to be calculated.
 *Return: None.
 */
void testScoreFor(struct gameState *state){
   int testScore, preScore; //Used for testing scoreFor()

   //Calculate the score in two different ways.
   preScore = preScoreFor(state->whoseTurn, state);
   testScore = scoreFor(state->whoseTurn, state);

   //Check that the same score was calculated both ways.
   assert(preScore == testScore);
}

int testWhoseTurn(int currentTurn, struct gameState *state){

   assert(currentTurn == state->whoseTurn);

   endTurn(state); //This will advance turn to next player.

   //Manually advance turn of currentTurn.
   if (currentTurn == ((state->numPlayers) - 1)) {
      currentTurn = 0;
   }
   else {
      currentTurn++;
   }

   //Check that the values to track whose turn are still the same.
   assert(currentTurn == state->whoseTurn);

   return currentTurn;
}


/*This function will calculate the winners in two ways,
 *and then compare the results to make sure they are the same.
 *1)The getwinners() function of dominion.c will be used.
 *2)The winners will be determined manually.
 *Params: state - a pointer to a struct gameState that contains.
 *Return: None.
 */
void testGetWinners(struct gameState *state) {
   int i;
   int highScore = -9999;
   int playersTest[MAX_PLAYERS];
   int playersCheck[MAX_PLAYERS];

   //Get a result using getWinners()
   getWinners(playersTest, state);

   //Get a result manually

   //Calculate score.
   for (i = 0; i < state->numPlayers; i++){
      playersCheck[i] = preScoreFor(i, state);
   }
   //Determine highest score
   for (i = 0; i < state->numPlayers; i++){
      if (playersCheck[i] > highScore){
         highScore = playersCheck[i];
      }
   }
   //If a player has the highscore and has had fewer turns, add one to their score.
   for (i = 0; i < state->numPlayers; i++){
      if (playersCheck[i] == highScore && i > state->whoseTurn){
         playersCheck[i]++;
      }
   }
   //Determine the highest score again.
   for (i = 0; i < state->numPlayers; i++){
      if (playersCheck[i] > highScore){
         highScore = playersCheck[i];
      }
   }

   //Find the winner(s)
   for (i = 0; i < state->numPlayers; i++){
      if (playersCheck[i] == highScore){
         playersCheck[i] = 1;
      }
      else {
         playersCheck[i] = 0;
      }
   }

   //End get a result manually.

   //Check that results of two methods are the same.
   for (i = 0; i < state->numPlayers; i++){
      assert(playersTest[i] == playersCheck[i]);
   }
}


/*Sets up a gameState struct, modifies it for the purpose of testing, and tests
 *the functions scoreFor(), getWinners(), whoseTurn(), and isGameOver()
 *Params: None.
 *Return: None.
 */
void runTests() {

   int *kingCards;
   struct gameState state;
   int currentTurn = 0;

   //I have no idea why I can't delete this line of code,
   //but when I do the program won't run.
   int *whoWins = malloc(sizeof(int) * MAX_PLAYERS);
   int returnValue;

   //Clear the game state and set parameters to use in call to initializeGame().
   memset(&state, 0, sizeof(struct gameState));
   kingCards = setRandomGameInitializers(&state);

   returnValue = initializeGame(numPlayers, kingCards, numSeed, &state);
   assert(returnValue == 0); //Make sure we have a valid gameState before running tests.

   //Now that gameState is initialized, modify some values for testing purposes.
   setPlayerCardPiles(&state);

   while (!isGameOver(&state)){

      testIsGameOver(&state);
      testScoreFor(&state);
      currentTurn = testWhoseTurn(currentTurn, &state);

      state.supplyCount[(int) (Random() * NUM_CARD_TYPES)]--; //Will eventually end the game.
   }

   testIsGameOver(&state);
   testGetWinners(&state);
}


int main (int argc, char** argv) {
   int i;
   int numGames = 5000;

   srand(time(NULL)); //Set up the RNG that I use (rand)
   PlantSeeds(-1); //Set up the RNG that functions in dominion use (rngs.c)

   //Run integration test.
   for (i = 0; i < numGames; i++) {
      runTests();
   }
   printf("All tests passed!\n\n");

   return 0;
}
